package leaderElection;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import centralRepository.CentralRepository;
import centralRepository.ServerObject;
import centralRepository.ServerObjectImpl;

public class LeaderElectionListener implements Runnable{

	private int portNum, id;
	private String name;
	private ServerObjectImpl so;
	private boolean returnValue;

	private boolean isCoordinator=false;

	private ArrayList<ServerObject> higherIds = new ArrayList<ServerObject>();

	private final int BUFFER_LENGTH=30;
	private final int ID_LENGTH=3;
	private final int SERVER_NAME_LENGTH=10;
	private final int MESSAGE_LENGTH=17;
	private final int LEADER_ELECTION_TIMEOUT_SECS=2;

	DatagramSocket server;

	private final boolean DEBUG=true;

	private String[] messages = {"Election", "Coordinator", "Not Coordinator"};

	public LeaderElectionListener(ServerObjectImpl so) {
		this.so=so;
		this.portNum=Integer.parseInt(so.getLePortNum());
		this.name=so.getName();
		this.id=Integer.parseInt(so.getId());
		refreshList();
		try {
			server = new DatagramSocket(portNum);
		} catch (SocketException e) {
			e.printStackTrace();
		}
	}

	public void refreshList() {
		higherIds.clear();
		ArrayList<ServerObject> orr=CentralRepository.getOtherRunningReplicas(so);
		if (orr.isEmpty()) {
			newLeader();
		}
		else {
			for (int i=0;i<orr.size();++i) {
				System.out.println(orr.get(i).getName()+" IS LESS THAN "+so.getName());
				if (Integer.parseInt(orr.get(i).getId())>id)
				{
					higherIds.add(orr.get(i));
				}

			}
			if (higherIds.isEmpty()) 
			{
				newLeader();
			}
		}
	}

	public void run() {
		System.out.println(so.getName() + " LEADER ELECTION STARTING");
		handlePackets();
	}

	private void handlePackets() {
		//Servers wait for leader election request packets to come in
		try {
			while (true) {
				byte[] inBuf=new byte[BUFFER_LENGTH];
				DatagramPacket inPacket = new DatagramPacket(inBuf, inBuf.length);
				DatagramPacket outPacket;

				//receive message
				server.receive(inPacket);

				InetAddress clientAddress = inPacket.getAddress();
				int clientPort = inPacket.getPort();
				byte[] outBuf = new byte[BUFFER_LENGTH];

				if (DEBUG)
					System.out.println("SENDER ID: "+getId(inPacket)+"\nSENDER NAME: "+getId(inPacket)+"\nSENDER MESSAGE: "+getMessage(inPacket));

				//verify request and prepare reply
				if (getMessage(inPacket).equals(messages[0])) //receive election message
				{
					isCoordinator=false;
					String message = "Not Coordinator";

					refreshList();

					//send reply
					if (!isCoordinator)
						findLeader();
					if (isCoordinator) {
						message = "Coordinator";
					}

					String idWithSpace = String.valueOf(id);
					for (int i=idWithSpace.length();i<ID_LENGTH;++i)
						idWithSpace+= " ";
					assert(idWithSpace.length()==ID_LENGTH);
					for (int i=name.length();i<SERVER_NAME_LENGTH;++i)
						name+= " ";
					assert(idWithSpace.length()==ID_LENGTH);					

					outBuf = (""+id+name+message).getBytes();
					outPacket = new DatagramPacket(outBuf, 0, outBuf.length, clientAddress, clientPort);
					server.send(outPacket);
				}
			}

		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	public int getId(DatagramPacket inPacket) {
		return Integer.parseInt(new String(inPacket.getData(), 0, ID_LENGTH).trim());
	}

	public String getName(DatagramPacket inPacket) {
		return new String(inPacket.getData(), ID_LENGTH, ID_LENGTH+SERVER_NAME_LENGTH).trim();
	}

	public String getMessage(DatagramPacket inPacket) {
		return new String(inPacket.getData(), ID_LENGTH+SERVER_NAME_LENGTH, BUFFER_LENGTH).trim();
	}

	public void findLeader() {
		sendElectionReq();
		if(getElectionResp())
			newLeader();
	}

	private void newLeader() {
		System.out.println("********************************NEW LEADER: "+so.getName()+"*************************************");
		isCoordinator=true;
		if (CentralRepository.getLeader(so)!=null) {
			System.out.println("OLD LEADER: "+CentralRepository.getLeader(so).getName());
			CentralRepository.getLeader(so).setLeader(false);
		}
		so.setLeader(true);
	}

	public void sendElectionReq() {
		for (int j=0;j<higherIds.size();++j) {
			String message=messages[0];
			DatagramPacket outPacket;
			byte[] outBuf = new byte[BUFFER_LENGTH];
			String idWithSpace = String.valueOf(id);
			for (int i=idWithSpace.length();i<ID_LENGTH;++i)
				idWithSpace+= " ";
			assert(idWithSpace.length()==ID_LENGTH);
			for (int i=name.length();i<SERVER_NAME_LENGTH;++i)
				name+= " ";
			assert(idWithSpace.length()==ID_LENGTH);
			outBuf = (""+id+name+message).getBytes();
			try {
				outPacket = new DatagramPacket(outBuf, 0, outBuf.length, InetAddress.getByName(higherIds.get(j).getHostname()), Integer.parseInt(higherIds.get(j).getLePortNum()));
				server.send(outPacket);
			} catch (NumberFormatException e) {
				e.printStackTrace();
			} catch (UnknownHostException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public boolean getElectionResp() {
		returnValue=true;
		Timer timer = new Timer();
		timer.schedule(new TimerTask() {
			@Override
			public void run() {
				while (returnValue) {
					byte[] inBuf=new byte[BUFFER_LENGTH];
					DatagramPacket inPacket = new DatagramPacket(inBuf, inBuf.length);
					try {
						server.receive(inPacket);
						if (getMessage(inPacket).equals(messages[1])||getMessage(inPacket).equals(messages[2]))
							returnValue =  false;//you are not leader
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		},  LEADER_ELECTION_TIMEOUT_SECS*1000); //2 second timer
		return returnValue;
	}



}
